/**
 * Class: Particle
 *
 * Purpose: 
 *
 * Created: Jan 24, 2010 @ 12:03:58 AM
 * @author Ken Scott
 */
package org.openfantasia.ps.particle;

import org.openfantasia.ps.Space;
import org.openfantasia.ps.actor.Actor;
import org.openfantasia.ps.modifier.Behavior;
import org.openfantasia.ps.modifier.behaviors.BehaviorManager;
import org.openfantasia.time.Tickable;
import org.openfantasia.ui.PColor;
import org.openfantasia.util.Copyable;
import org.openfantasia.util.listener.Event;
import org.openfantasia.util.listener.Listener;
import org.openfantasia.util.math.DPoint3;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Particle implements Listener, Tickable, Copyable<Particle> {

	public static final double NEWBORN = Double.NEGATIVE_INFINITY;

	public static final double IMMORTAL = Double.POSITIVE_INFINITY;

	protected Space space;

	protected ParticleFactory factory;

	protected Particle parent;

	protected List<Particle> children = new ArrayList<Particle>();

	protected Map<String, Object> attributes = new HashMap<String, Object>();

	protected boolean visible = true;

	protected DPoint3 referencePosition = new DPoint3();

	protected DPoint3 previousPosition = new DPoint3();

	protected DPoint3 position = new DPoint3();

	protected DPoint3 velocity = new DPoint3();

	protected DPoint3 acceleration = new DPoint3();

	protected DPoint3 previousOrientation = new DPoint3();

	protected DPoint3 orientation = new DPoint3();

	protected DPoint3 spin = new DPoint3();

	protected DPoint3 spinAcceleration = new DPoint3();

	protected DPoint3 size = new DPoint3(1, 1, 1);

	protected double brightness = 1.0d;

	protected double opacity = 1.0d;

	protected PColor fillColor = PColor.NONE;

	protected PColor lineColor = PColor.WHITE;

	protected double lineWeight = 1;

	protected double mass = 1.0d;

	protected double bounce = 0.5d;

	protected double lifespan = IMMORTAL;

	protected double timeOfBirth = NEWBORN;

	protected double timeOfDeath = IMMORTAL;

	protected boolean forceSensitive = true;

	protected Actor actor;

	protected Space.Action onBirth;

	protected Space.Action onDeath;

	protected BehaviorManager behaviors = new BehaviorManager();

	protected List<Activity> activities = new ArrayList<Activity>();


	public Particle() {
	}


	public Particle(DPoint3 position) {
		this.previousPosition = position.copy();
		this.position = position.copy();
		this.referencePosition = position.copy();
	}


	public Particle(DPoint3 position, DPoint3 velocity) {
		this.previousPosition = position.copy();
		this.position = position.copy();
		this.referencePosition = position.copy();
		this.velocity = velocity;
	}


	public Particle(DPoint3 position, DPoint3 velocity, DPoint3 acceleration) {
		this.previousPosition = position.copy();
		this.position = position.copy();
		this.referencePosition = position.copy();
		this.velocity = velocity;
		this.acceleration = acceleration;
	}


	public Particle(DPoint3 position, DPoint3 velocity, DPoint3 acceleration, DPoint3 orientation, DPoint3 spin, DPoint3 spinAcceleration) {
		this.previousPosition = position;
		this.position = position;
		this.velocity = velocity;
		this.acceleration = acceleration;
		this.orientation = orientation;
		this.spin = spin;
		this.spinAcceleration = spinAcceleration;
	}


	public Particle(Particle that) {
		copy(that);
	}


	public void copy(Particle that) {
		this.space = that.space;
		this.factory = that.factory;
		this.parent = that.parent;
		this.children = new ArrayList<Particle>();
		for (Particle child : that.children) {
			child = child.copy();
			child.setParent(null);
			addChild(child);
		}
		this.attributes = new HashMap<String, Object>(that.attributes);
		this.visible = that.visible;
		this.previousPosition = new DPoint3(that.previousPosition);
		this.position = new DPoint3(that.position);
		this.velocity = new DPoint3(that.velocity);
		this.acceleration = new DPoint3(that.acceleration);
		this.previousOrientation = new DPoint3(that.previousOrientation);
		this.orientation = new DPoint3(that.orientation);
		this.spin = new DPoint3(that.spin);
		this.spinAcceleration = new DPoint3(that.spinAcceleration);
		this.size = new DPoint3(that.size);
		this.brightness = that.brightness;
		this.opacity = that.opacity;
		this.fillColor = (that.fillColor != null) ? new PColor(that.fillColor) : null;
		this.lineColor = (that.lineColor != null) ? new PColor(that.lineColor) : null;
		this.lineWeight = that.lineWeight;
		this.bounce = that.bounce;
		this.mass = that.mass;
		this.lifespan = that.lifespan;
		this.timeOfBirth = that.timeOfBirth;
		this.timeOfDeath = that.timeOfDeath;
		this.forceSensitive = that.forceSensitive;
		this.actor = that.actor;
		this.onBirth = that.onBirth;
		this.onDeath = that.onDeath;
	}


	public Particle copy() {
		return new Particle(this);
	}


	public Particle getParent() {
		return parent;
	}


	public void setParent(Particle parent) {
		this.parent = parent;
	}


	public List<Particle> getChildren() {
		return children;
	}


	public void setChildren(List<Particle> children) {
		this.children = children;
	}


	public Particle addChild(Particle child) {
		if (child.getParent() != null) {
			throw new IllegalArgumentException("Child particle already has a parent");
		}
		if (!this.children.contains(child)) {
			this.children.add(child);
			child.setParent(this);
		}
		return child;
	}


	public Particle removeChild(Particle child) {
		if (this.children.contains(child)) {
			this.children.remove(child);
			child.setParent(null);
		}
		return child;
	}


	public void clear() {
		timeOfBirth = NEWBORN;
	}


	public void applyForce(DPoint3 force) {
		DPoint3 f = force.copy().scale(1.0 / mass);
		acceleration.add(f);
	}


	public void tick(double time, double dt) {
		if (timeOfBirth == NEWBORN) {
			birth(time);
		}

		if ((lifespan != IMMORTAL) && (time >= (timeOfBirth + lifespan))) {
			death(time);
		}
		else {
			for (Activity activity : activities) {
				if (activity.isActive(this, time)) {
					activity.action(this, time);
				}
			}

			update(time, dt);

			for (Particle ch : children) {
				ch.tick(time, dt);
			}
		}
	}


	public void birth(double time) {
		timeOfBirth = time;

		if (onBirth != null) {
			onBirth.action();
		}
	}


	public void death(double time) {
		timeOfDeath = time;

		if (onDeath != null) {
			onDeath.action();
		}

		getSpace().removeParticle(this);
		if (factory != null) {
			factory.put(this);
		}
	}


	public void update(double time, double dt) {
		if (isForceSensitive()) {
			DPoint3 pdt = new DPoint3(dt, dt, dt);

			// Update velocity & position
			velocity.add(acceleration.copy().mul(pdt));
			setPosition(position.copy().add(velocity.copy().mul(pdt)));

			// Update spin & orientation
			spin.add(spinAcceleration.copy().mul(pdt));
			setOrientation(orientation.copy().add(spin.copy().mul(pdt)));

			for (Particle ch : children) {
				ch.update(time, dt);
			}
		}
	}


	public void draw() {
		if (isVisible()) {
			if (getActor() != null) {
				getActor().draw(this);
			}

			for (Particle p : children) {
				p.draw();
			}
		}
	}


	public ParticleFactory getFactory() {
		return factory;
	}


	public void setFactory(ParticleFactory factory) {
		this.factory = factory;
	}


	public Map<String, Object> getAttributes() {
		return attributes;
	}


	public void setAttributes(Map<String, Object> attributes) {
		this.attributes = attributes;
	}


	public Object getAttribute(String name) {
		return attributes.get(name);
	}


	public void setAttribute(String name, Object value) {
		attributes.put(name, value);
	}


	public Space getSpace() {
		return space;
	}


	public void setSpace(Space space) {
		this.space = space;
	}


	public boolean isVisible() {
		return visible;
	}


	public void setVisible(boolean visible) {
		this.visible = visible;
	}


	public DPoint3 getPreviousPosition() {
		return previousPosition;
	}


	public void setPreviousPosition(DPoint3 previousPosition) {
		this.previousPosition = previousPosition;
	}


	public DPoint3 getPosition() {
		return position;
	}


	public void setPosition(DPoint3 position) {
		this.previousPosition = this.position.copy();
		this.position = new DPoint3(position);
	}


	public DPoint3 getReferencePosition() {
		return referencePosition;
	}


	public void setReferencePosition(DPoint3 referencePosition) {
		this.referencePosition = referencePosition;
	}


	public DPoint3 getVelocity() {
		return velocity;
	}


	public void setVelocity(DPoint3 velocity) {
		this.velocity = new DPoint3(velocity);
	}


	public DPoint3 getAcceleration() {
		return acceleration;
	}


	public void setAcceleration(DPoint3 acceleration) {
		this.acceleration = new DPoint3(acceleration);
	}


	public DPoint3 getPreviousOrientation() {
		return previousOrientation;
	}


	public void setPreviousOrientation(DPoint3 previousOrientation) {
		this.previousOrientation = previousOrientation;
	}


	public DPoint3 getOrientation() {
		return orientation;
	}


	public void setOrientation(DPoint3 orientation) {
		this.previousOrientation = this.orientation;
		this.orientation = orientation;
	}


	public DPoint3 getSpin() {
		return spin;
	}


	public void setSpin(DPoint3 spin) {
		this.spin = spin;
	}


	public DPoint3 getSpinAcceleration() {
		return spinAcceleration;
	}


	public void setSpinAcceleration(DPoint3 spinAcceleration) {
		this.spinAcceleration = spinAcceleration;
	}


	public DPoint3 getSize() {
		return size;
	}


	public void setSize(DPoint3 size) {
		this.size = size;
	}


	public void setSize(double size) {
		this.size = new DPoint3(size, size, size);
	}


	public double getBrightness() {
		return brightness;
	}


	public void setBrightness(double brightness) {
		this.brightness = brightness;
		for (Particle p : children) {
			p.setBrightness(brightness);
		}
	}


	public double getOpacity() {
		return opacity;
	}


	public void setOpacity(double opacity) {
		this.opacity = opacity;
		for (Particle p : children) {
			p.setOpacity(opacity);
		}
	}


	public PColor getFillColor() {
		return fillColor;
	}


	public void setFillColor(PColor fillColor) {
		this.fillColor = fillColor;
		for (Particle p : children) {
			p.setFillColor(fillColor);
		}
	}


	public PColor getLineColor() {
		return lineColor;
	}


	public void setLineColor(PColor lineColor) {
		this.lineColor = lineColor;
		for (Particle p : children) {
			p.setLineColor(lineColor);
		}
	}


	public double getLineWeight() {
		return lineWeight;
	}


	public void setLineWeight(double lineWeight) {
		this.lineWeight = lineWeight;
		for (Particle p : children) {
			p.setLineWeight(lineWeight);
		}
	}


	public double getMass() {
		return mass;
	}


	public void setMass(double mass) {
		this.mass = mass;
		for (Particle p : children) {
			p.setMass(mass);
		}
	}


	public double getBounce() {
		return bounce;
	}


	public void setBounce(double bounce) {
		this.bounce = bounce;
		for (Particle p : children) {
			p.setBounce(bounce);
		}
	}


	public double getLifespan() {
		return lifespan;
	}


	public void setLifespan(double lifespan) {
		this.lifespan = lifespan;
		for (Particle p : children) {
			p.setLifespan(lifespan);
		}
	}


	public double getTimeOfBirth() {
		return timeOfBirth;
	}


	public double getTimeOfDeath() {
		return timeOfDeath;
	}


	public boolean isForceSensitive() {
		return forceSensitive;
	}


	public void setForceSensitive(boolean forceSensitive) {
		this.forceSensitive = forceSensitive;
		for (Particle p : children) {
			p.setForceSensitive(forceSensitive);
		}
	}


	public Actor getActor() {
		return actor;
	}


	public void setActor(Actor actor) {
		this.actor = actor;
	}


	public Space.Action getOnBirth() {
		return onBirth;
	}


	public void setOnBirth(Space.Action onBirth) {
		this.onBirth = onBirth;
	}


	public Space.Action getOnDeath() {
		return onDeath;
	}


	public void setOnDeath(Space.Action onDeath) {
		this.onDeath = onDeath;
	}


	public void addBehavior(String name, Behavior bhv) {
		behaviors.add(name, bhv);
	}


	public void removeBehavior(String name, Behavior bhv) {
		behaviors.remove(name, bhv);
	}


	public Object getBehaviorValue(String name, double time, Object param) {
		return behaviors.getBehaviorValue(name, this, time, param);
	}


	public void addActivity(Activity activity) {
		activities.add(activity);
	}


	public void removeActivity(Activity activity) {
		activities.remove(activity);
	}


	public void receive(Event event) {

	}
}
